interface ITodo {
  id: number;
  content: string;
  completed: boolean;
}

//定义多个类型在一个联合类型里
type TypeTarget = HTMLInputElement | HTMLButtonElement;

type TypeContentMap = {
  [key: number]: HTMLSpanElement;
  //给类型定义一个签名函数属性 属性名称为remove 是一个函数
  remove(id: number): void;
};

//定义枚举类型是定义一组常量，使其更加容易维护
enum ElementType {
  CHECKBOX = 'HTMLInputElement',
  BUTTON = 'HTMLButtonElement'
}

//写法一： as 断言
const oTodoInput = document.querySelector('#inputText') as HTMLInputElement;
//写法二： <> 断言
// const oTodoInput = <HTMLInputElement>document.querySelector('#inputText');
const oAddBtn = document.querySelector('#addBtn');
const oTodoList = document.querySelector('#todoList');

/**
 * 通过Map数据对所有事件进行统一管理。将所有的事件绑定列举出来，通过遍历进行访问，是其看起来更加的立体，并弱化bindEvent函数，不用关心bindEvent的实现，此写法有利于日后代码维护。好的设计使项目的生命周期变得更长。
 */
const eventMap = new Map([
  [oAddBtn, handleAddBtnClick],
  [oTodoList, handleListClick]
]);

//Map{ id: Node } 定义一个对象数据来管理对应id的span节点
const contentMap: TypeContentMap = {
  remove: (id: number) => delete contentMap[id]
};

const init = () => {
  bindEvent();
};

function bindEvent() {
  eventMap.forEach((fn, el) => {
    el?.addEventListener('click', fn, false);
  });
}

function handleAddBtnClick() {
  const inputText = oTodoInput.value;
  //输入框无内容时不往下执行
  if (!inputText.trim().length) return;
  //将列表项插入到列表中
  oTodoList?.appendChild(
    createTodoItem({
      id: new Date().getTime(),
      content: inputText,
      completed: false
    })
  );
  oTodoInput.value = '';
}

//事件代理：通过事件源对象找到复选框按钮和删除按钮
function handleListClick(e: Event) {
  const target = e.target as TypeTarget;
  const type = getObjectType(target);
  // console.log(type);
  //HTMLInputElement | HTMLButtonElement | HTMLSpanElement

  //点击了复选框或删除按钮时
  if (type === ElementType.CHECKBOX || type === ElementType.BUTTON) {
    const id = Number(target.dataset.id);
    switch (type) {
      case ElementType.CHECKBOX:
        const completed = (target as HTMLInputElement).checked;
        setContentStyle(id, completed);
        break;
      case ElementType.BUTTON:
        removeTodoItem(id, target.parentNode!);
        break;
      default:
        break;
    }
  }
}

/**
 * 该todo是一个对象包含以下属性：
 * todo {
 *   id: number,
 *   content: string,
 *   completed: boolean
 * }
 */
function createTodoItem({ id, content, completed }: ITodo): HTMLElement {
  //创建<li></li>
  const oTodoItem = document.createElement('li');
  //根据<input id="xxx" type="checkbox" />中的id属性来对文本的样式更改
  oTodoItem.appendChild(createCheckBox(id, completed));
  //创建文本内容 <span>{{content}}</span>
  oTodoItem.appendChild(createContent(id, content));
  //创建删除按钮 <button>Remove</button>
  oTodoItem.appendChild(createRemoveBtn(id));
  return oTodoItem;
}

function createCheckBox(id: number, completed: boolean): HTMLInputElement {
  const oCheckBox = document.createElement('input');
  oCheckBox.type = 'checkbox';
  //设置checked默认值为todo对象里completed属性的值
  oCheckBox.checked = completed;
  oCheckBox.dataset.id = id.toString();
  return oCheckBox;
}

function createContent(id: number, content: string): HTMLSpanElement {
  const oContent = document.createElement('span');
  oContent.innerText = content;
  contentMap[id] = oContent;
  return oContent;
}

function createRemoveBtn(id: number): HTMLButtonElement {
  const oRemoveBtn = document.createElement('button');
  oRemoveBtn.innerText = 'Remove';
  oRemoveBtn.dataset.id = id.toString();
  return oRemoveBtn;
}

function removeTodoItem(id: number, item: ParentNode): void {
  oTodoList?.removeChild(item);
  contentMap.remove(id);
}

/**
 * 根据id和completed来修改<span>content</span>是否有中横线
 */
function setContentStyle(id: number, completed: boolean): void {
  contentMap[id].style.textDecoration = completed ? 'line-through' : 'none';
}

// 获取对象类型
function getObjectType<T>(value: T): string {
  //[object ...]
  return Object.prototype.toString.call(value).match(/\[object (.+?)\]/)[1];
}

init();
